﻿#region Copyright Notice
// P r o p r i e t a r y   N o t i c e                                            
// Copyright (C) 2010 Eclipsys Corporation. Unpublished. All Rights Reserved.     
//                                                                                
// P r o p r i e t a r y   N o t i c e: This software is the confidential and     
// proprietary information of Eclipsys Corporation, protected by trade secret     
// and copyright law.  This software and all related rights are the exclusive     
// property of Eclipsys Corporation.  All use, modification, reproduction,        
// release, performance, display and disclosure governed by Eclipsys Corporation  
// license terms.  U.S. Government Users:  This software is "Commercial Computer  
// Software" within the meaning of FAR 2.101 (November 2007), FAR Part 12.212     
// (October 1995), FAR 27.405-3 (November 2007), DFARS Part 227.7202 (June 1995)  
// and DFARS 252.227-7014 (a) (June 1995).  Manufacturer is Eclipsys Corporation, 
// Three Ravinia Drive, Atlanta, GA  30346.                                       
// P r o p r i e t a r y   N o t i c e                                            
//                                                                                
// Confidential and proprietary information of Eclipsys Corporation.  Authorized  
// users only. U.S. Government Users:  "Commercial Computer Software." Subject    
// to full notice set forth herein.                                               
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace Informant.UI
{

	/// <summary>
	/// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
	/// </summary>
	/// <typeparam name="T">Parameter type.</typeparam>
	public class DelegateCommand<T> : ICommand
	{
		private readonly Action<T> _executeMethod;
		private readonly Func<T, bool> _canExecuteMethod;
		private Dispatcher _dispatcher;
		private readonly List<string> _triggerProperties;
		private bool _triedDispatcher2;

		/// <summary>
		/// Constructor. Initializes delegate command with Execute delegate.
		/// </summary>
		/// <param name="executeMethod">Delegate to execute when Execute is called on the command.  This can be null to just hook up a CanExecute delegate.</param>
		/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
		public DelegateCommand( Action<T> executeMethod )
			: this( executeMethod, null )
		{
		}


		/// <summary>
		/// Constructor. Initializes delegate command with Execute delegate and CanExecute delegate
		/// </summary>
		/// <param name="executeMethod">Delegate to execute when Execute is called on the command.  This can be null to just hook up a CanExecute delegate.</param>
		/// <param name="canExecuteMethod">Delegate to execute when CanExecute is called on the command.  This can be null.</param>
		public DelegateCommand( Action<T> executeMethod, Func<T, bool> canExecuteMethod )
			: this( executeMethod, canExecuteMethod, null )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DelegateCommand&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="executeMethod">Delegate to execute when Execute is called on the command.  This can be null to just hook up a CanExecute delegate.</param>
		/// <param name="canExecuteMethod">Delegate to execute when CanExecute is called on the command.  This can be null.</param>
		/// <param name="updateTriggerProperties">The set of property names that should be used to trigger calls to CanExecute.</param>
		public DelegateCommand( Action<T> executeMethod, Func<T, bool> canExecuteMethod, IEnumerable<string> updateTriggerProperties )
		{
			if ( executeMethod == null && canExecuteMethod == null )
				throw new ArgumentNullException( "executeMethod", "Both the executeMethod and the canExecuteMethod delegates cannot be null." );

			_executeMethod = executeMethod;
			_canExecuteMethod = canExecuteMethod;
			if ( Application.Current != null && Application.Current.RootVisual != null )
			{
				_dispatcher = System.Windows.Application.Current.RootVisual.Dispatcher;
			}

			_triggerProperties = updateTriggerProperties != null ? new List<string>( updateTriggerProperties ) : new List<string>();

			if ( _canExecuteMethod != null )
			{
				INotifyPropertyChanged target = _canExecuteMethod.Target as INotifyPropertyChanged;
				if ( target != null )
				{
					target.PropertyChanged += TriggerPropertyChanged;
				}
			}
		}

		///<summary>
		///Defines the method that determines whether the command can execute in its current state.
		///</summary>
		///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
		///<returns>
		///true if this command can be executed; otherwise, false.
		///</returns>
		public bool CanExecute( T parameter )
		{
			if ( _canExecuteMethod == null ) 
				return true;
				
			return _canExecuteMethod( (T)parameter );
		}

		///<summary>
		///Defines the method to be called when the command is invoked.
		///</summary>
		///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
		public void Execute( object parameter )
		{
			if ( _executeMethod == null ) 
				return;

			_executeMethod( (T)parameter );
		}

		//private static object ConvertParameter( object parameter )
		//{
		//    if ( parameter != null )
		//    {
		//        if ( parameter is T )
		//            return (T)parameter;

		//        TypeConverter converter = TypeDescriptor.GetConverter( typeof( T ) );
		//        if ( converter.IsValid( parameter ) )
		//            return (T)converter.ConvertFrom( parameter );
		//    }

		//    return default( T );
		//}

		///<summary>
		///Defines the method that determines whether the command can execute in its current state.
		///</summary>
		///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
		///<returns>
		///true if this command can be executed; otherwise, false.
		///</returns>
		bool ICommand.CanExecute( object parameter )
		{
			if ( parameter == null )
			{
				return CanExecute( default(T) );
			}

			// special handling for Silverlight's treatment of Boolean types for CommandParameters
			// In XAML, we declare a Boolean, but the type created by the xaml parser is a UInt32
			// So we look for that case and cast as Bool to satisfy the T argument 
			// (same as in Execute)
			if ( typeof( T ) == typeof( bool ) && parameter.GetType() == typeof( UInt32 ) )
			{
				bool param = parameter.ToString() == "1";
				return CanExecute( (T)((object)param) );
			}
			return CanExecute( (T)parameter );
		}

		///<summary>
		///Occurs when changes occur that affect whether or not the command should execute.
		///</summary>
		public event EventHandler CanExecuteChanged = delegate { };

		///<summary>
		///Defines the method to be called when the command is invoked.
		///</summary>
		///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
		void ICommand.Execute( object parameter )
		{
			// special handling for Silverlight's treatment of Boolean types for CommandParameters
			// In XAML, we declare a Boolean, but the type created by the xaml parser is a UInt32
			// So we look for that case and cast as Bool to satisfy the T argument 
			// (same as in CanExecute)
			if ( typeof( T ) == typeof( bool ) && parameter.GetType() == typeof( UInt32 ) )
			{
				bool param = parameter.ToString() == "1";
				Execute( (T)((object)param) );
				return;
			}
			Execute( (T)parameter );
		}

		/// <summary>
		/// Raises <seealso cref="CanExecuteChanged"/> on the UI thread.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected virtual void OnCanExecuteChanged( object sender, EventArgs e )
		{
			// special check for Silverlight apps.  Application.Current not set until after initial parsing of XAML where the 
			// DelegateCommand bindings are hooked up.  If _dispatcher is not set up in this class ctor, we'll try one more time here
			if ( _dispatcher == null && !_triedDispatcher2 )
			{
				if ( Application.Current != null && Application.Current.RootVisual != null )
				{
					_dispatcher = Application.Current.RootVisual.Dispatcher;
				}

				_triedDispatcher2 = true;
			}

			if (_dispatcher == null)
			{
				return;
			}

			if (_dispatcher.CheckAccess())
			{
				_dispatcher.BeginInvoke(() => CanExecuteChanged(sender, e));
			}
			else
			{
				_dispatcher.BeginInvoke(new EventHandler(OnCanExecuteChanged),
				                        sender,
				                        e);
				return;
			}
		}

		/// <summary>
		/// Raises <see cref="CanExecuteChanged"/> so every command invoker can requery to check if the command can execute.
		/// <remarks>This will trigger the execution of <see cref="CanExecute"/> once for each invoker.</remarks>
		/// </summary>
		public void RaiseCanExecuteChanged()
		{
			OnCanExecuteChanged( this, EventArgs.Empty );
		}

		private void TriggerPropertyChanged( object sender, PropertyChangedEventArgs e )
		{
			if ( _triggerProperties.Contains( e.PropertyName ) )
				RaiseCanExecuteChanged();
		}
	}
}
